<?php

namespace butpay\ali;

/**
 * 支付宝支付接口集合
 */
class AliPay
{
    const URL = 'openapi.alipay.com/gateway.do'; //正式
    // const URL = 'openapi-sandbox.dl.alipaydev.com/gateway.do'; //沙箱
    const VERSION = '1.0';
    const FORMAT = 'json';
    const CHARSET = 'UTF-8';
    const SIGN_TYPE = 'RSA2';


    /**
     * 扫码支付预下单（获取支付二维码）
     *
     * @param string $appId 应用ID
     * @param string $privatePem 应用私钥
     * @param string $subject 订单主题
     * @param string $outTradeNo 订单号
     * @param integer $totalAmount 订单金额，单位分
     * @param string $notifyUrl 支付成功通知地址
     * @return array
     */
    public static function precreate(
        string $appId,
        string $privatePem,
        string $subject,
        string $outTradeNo,
        int $totalAmount,
        string $notifyUrl
    ): array {
        //公共参数
        $commonParams = [
            'method'      => 'alipay.trade.precreate',
            'app_id'      => $appId
        ];

        $bodyData = [
            'biz_content' => json_encode([
                'subject'      => $subject,
                'out_trade_no' => $outTradeNo,
                'total_amount' => $totalAmount / 100
            ], JSON_UNESCAPED_UNICODE),
            'notify_url'  => $notifyUrl
        ];

        $res = self::apoRequest($commonParams, $bodyData, $privatePem);
        if (!empty($res)) {
            $result = $res['alipay_trade_precreate_response'];
            if ($result['code'] == 10000) {
                return [
                    'code'         => $result['code'],
                    'msg'          => $result['msg'],
                    'out_trade_no' => $result['out_trade_no'],
                    'qr_code'      => $result['qr_code']
                ];
            } else {
                return [
                    'code'         => $result['code'],
                    'msg'          => $result['sub_msg']
                ];
            }
        }
        return [];
    }

    /**
     * 查询订单
     *
     * @param string $appId 应用ID
     * @param string $privatePem 应用私钥
     * @param string $outTradeNo 订单号
     * @return array
     */
    public static function queryOrder(string $appId, string $privatePem, string $outTradeNo): array
    {
        //公共参数
        $commonParams = [
            'method'      => 'alipay.trade.query',
            'app_id'      => $appId
        ];

        $bodyData = [
            'biz_content' => json_encode([
                'out_trade_no' => $outTradeNo,
            ], JSON_UNESCAPED_UNICODE)
        ];

        $res = self::apoRequest($commonParams, $bodyData, $privatePem);

        if (!empty($res)) {
            $result = $res['alipay_trade_query_response'];
            if ($result['code'] == 10000) {
                return $result;
            } else {
                return [
                    'code'         => $result['code'],
                    'msg'          => $result['sub_msg'],
                ];
            }
        }
        return [];
    }

    /**
     * 订单退款
     *
     * @param string $appId 应用ID
     * @param string $privatePem 应用私钥
     * @param string $outTradeNo 订单号
     * @param integer $refundAmount 退款金额，单位分
     * @return array
     */
    public static function refundOrder(string $appId, string $privatePem, string $outTradeNo, int $refundAmount): array
    {
        //公共参数
        $commonParams = [
            'method'      => 'alipay.trade.refund',
            'app_id'      => $appId
        ];

        $bodyData = [
            'biz_content' => json_encode([
                'out_trade_no' => $outTradeNo,
                'refund_amount' => $refundAmount / 100
            ], JSON_UNESCAPED_UNICODE)
        ];

        $res = self::apoRequest($commonParams, $bodyData, $privatePem);

        if (!empty($res)) {
            $result = $res['alipay_trade_refund_response'];
            if ($result['code'] == 10000) {
                if ($result['fund_change'] == 'Y') {
                    return $result;
                } else {
                    return [
                        'code' => 101,
                        'msg'  => '此订单已经退款'
                    ];
                }
            } else {
                return [
                    'code' => $result['code'],
                    'msg'  => $result['sub_msg']
                ];
            }
        }
        return [];
    }

    /**
     * 退款查询
     *
     * @param string $appId 应用ID
     * @param string $privatePem 应用私钥
     * @param string $outTradeNo 订单号
     * @return array
     */
    public static function refundQuery(string $appId, string $privatePem, string $outTradeNo): array
    {
        //公共参数
        $commonParams = [
            'method'      => 'alipay.trade.fastpay.refund.query',
            'app_id'      => $appId
        ];

        $bodyData = [
            'biz_content' => json_encode([
                'out_trade_no'   => $outTradeNo,
                'out_request_no' => $outTradeNo
            ], JSON_UNESCAPED_UNICODE)
        ];

        $res = self::apoRequest($commonParams, $bodyData, $privatePem);

        if (!empty($res)) {
            $result = $res['alipay_trade_fastpay_refund_query_response'];
            if ($result['code'] == 10000) {
                return $result;
            } else {
                return [
                    'code' => $result['code'],
                    'msg'  => $result['sub_msg']
                ];
            }
        }
        return [];
    }

    /**
     * 验证签名
     *
     * @param array $data 通知数据
     * @param string $publicKey 支付宝公钥
     * @return boolean
     */
    public static function verifySign(array $data, string $publicKey): bool
    {
        $sign = $data['sign'];
        unset($data['sign']);
        unset($data['sign_type']);

        $checkSign = self::buildSignContent($data);

        $pubKey = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($publicKey, 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";

        return (bool) openssl_verify($checkSign, base64_decode($sign), $pubKey, OPENSSL_ALGO_SHA256);
    }

    /**
     * 自动生成请求
     *
     * @param array $commonParams 公共参数
     * @param array $bodyData 请求参数
     * @param string $privatekey 应用私钥
     * @return array
     */
    private static function apoRequest(array $commonParams, array $bodyData, string $privatekey):array
    {
        $commonParams = array_merge($commonParams, [
            'timestamp'   => date('Y-m-d H:i:s'),
            'version'     => self::VERSION,
            'format'      => self::FORMAT,
            'charset'     => self::CHARSET,
            'sign_type'   => self::SIGN_TYPE,
        ]);
        $commonParams['sign'] = self::makeSign($commonParams, $bodyData, $privatekey);

        $url = self::URL . '?' . self::buildQueryString($commonParams);

        $bodyStr = self::buildQueryString($bodyData);

        $result = self::curlRequest($url, $bodyStr);

        $res = json_decode($result, true);

        return !empty($res) ? $res : [];
    }

    /**
     * 生成签名
     *
     * @param array $commonParams 公共参数
     * @param array $bodyData 请求参数
     * @param string $privatekey 应用私钥
     * @return string
     */
    private static function makeSign(array $commonParams, array $bodyData, string $privatekey):string
    {
        //构建代签名字符串
        $signData = array_merge($commonParams, $bodyData);

        $signStr = self::buildSignContent($signData);

        $privatekey = "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($privatekey, 64, "\n", true) .
            "\n-----END RSA PRIVATE KEY-----";

        openssl_sign($signStr, $sign, $privatekey, OPENSSL_ALGO_SHA256);
        return base64_encode($sign);
    }

    /**
     * 构建签名内容字符串
     *
     * @param array $data 待签名数据
     * @return string
     */
    private static function buildSignContent(array $data):string
    {
        ksort($data);

        $str = "";
        foreach ($data as $k => $v) {
            if (!empty($v) && "@" != substr($v, 0, 1)) {
                $str .= $k . "=" . $v . '&';
            }
        }
        return substr($str, 0, -1);
    }

    /**
     * 构建请求链接参数
     *
     * @param array $params
     * @return string
     */
    private static function buildQueryString(array $params):string
    {
        $str = "";
        foreach ($params as $k => $v) {
            if (!empty($v) && "@" != substr($v, 0, 1)) {
                $str .= $k . "=" . urlencode($v) . '&';
            }
        }
        return substr($str, 0, -1);
    }

    /**
     * 自定义发送请求方法
     *
     * @param string $url 请求URL
     * @param string $headerToken 请求header token
     * @param boolean $isPost 是否post请求
     * @param array $postData post 请求参数
     * @return string
     */
    private static function curlRequest(string $url, string $postData): string
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_TIMEOUT, 60);
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);

        if (!empty($postData)) {
            curl_setopt($curl, CURLOPT_POST, TRUE);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
        }

        $res = curl_exec($curl);
        if ($res === false) {
            $res = 'Curl error: ' . curl_error($curl);
        }

        curl_close($curl);

        return $res;
    }
}
